﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TileHelper 
{
    private static RaycastHit2D[] raycastHitBuffers = new RaycastHit2D[1];

    private static ValueTuple<float, float, Vector2, Vector2>[] directionTuples = new ValueTuple<float, float, Vector2, Vector2>[] {
        new ValueTuple<float, float, Vector2, Vector2>(0, StaticDataManager.BOUND_1_1, Vector2.up, Vector2.zero),
        new ValueTuple<float, float, Vector2, Vector2>(StaticDataManager.BOUND_1_1, StaticDataManager.BOUND_1_2, Vector2.up, Vector2.right),
        new ValueTuple<float, float, Vector2, Vector2>(StaticDataManager.BOUND_1_2, StaticDataManager.BOUND_2_1, Vector2.right, Vector2.zero),
        new ValueTuple<float, float, Vector2, Vector2>(StaticDataManager.BOUND_2_1, StaticDataManager.BOUND_2_2, Vector2.right, Vector2.down),
        new ValueTuple<float, float, Vector2, Vector2>(StaticDataManager.BOUND_2_2, StaticDataManager.BOUND_3_1, Vector2.down, Vector2.zero),
        new ValueTuple<float, float, Vector2, Vector2>(StaticDataManager.BOUND_3_1, StaticDataManager.BOUND_3_2, Vector2.down, Vector2.left),
        new ValueTuple<float, float, Vector2, Vector2>(StaticDataManager.BOUND_3_2, StaticDataManager.BOUND_4_1, Vector2.left, Vector2.zero),
        new ValueTuple<float, float, Vector2, Vector2>(StaticDataManager.BOUND_4_1, StaticDataManager.BOUND_4_2, Vector2.left, Vector2.up),
        new ValueTuple<float, float, Vector2, Vector2>(StaticDataManager.BOUND_4_2, 360f, Vector2.up, Vector2.zero),
    };

    public static GameObject GetTileByPosition(Vector3 position)
    {
        GameObject targetTile = null;

        Collider2D[] colliders = Physics2D.OverlapPointAll(position);

        foreach (Collider2D collider in colliders)
        {
            if (collider.CompareTag(StaticDataManager.TILE_TAG))
            {
                targetTile = collider.gameObject;
                break;
            }
        }

        return targetTile;
    }

    public static GameObject GetGameObjectWithSprite(Sprite sprite, Color color)
    {
        GameObject gameObject = new GameObject();
        gameObject.AddComponent<SpriteRenderer>();
        gameObject.GetComponent<SpriteRenderer>().sprite = sprite;
        gameObject.GetComponent<SpriteRenderer>().color = color;

        return gameObject;
    }

    public static void AddScoreToTargetTile(GameObject target, Vector2 direction, float distance, int score)
    {
        int currentTurnCount = GameObject.FindGameObjectWithTag(StaticDataManager.GAME_SESSION_TAG).GetComponent<GameSession>().currentTurnCount;

        RaycastHit2D hit = Physics2D.Raycast(target.transform.position, direction, distance);
        if (hit)
        {
            TileMonoBehaviour tile = hit.collider.GetComponent<TileMonoBehaviour>();

            if (tile && tile.turnCounter <= currentTurnCount)
            {
                tile.ResetScore();
                tile.AddScore(score);

                tile.turnCounter = currentTurnCount;
            }
        }
    }

    public static bool isObjectExistAmongDirection(GameObject from, Vector2 direction, string layerName = "", float maxDistance = 1f)
    {
        Physics2D.RaycastNonAlloc(from.transform.position, direction, raycastHitBuffers, 0.7f, LayerMask.GetMask(layerName));

        if (raycastHitBuffers[0].collider)
        {
            return true;
        }
        
        return false;
    }

    public static Dictionary<Vector2, TileMonoBehaviour> GetNearbyTile(GameObject target, float distance)
    {
        Dictionary<Vector2, TileMonoBehaviour> result = new Dictionary<Vector2, TileMonoBehaviour>();

        foreach (Vector2 direction in new Vector2[] { Vector2.up, Vector2.down, Vector2.left, Vector2.right })
        {
            RaycastHit2D hit = Physics2D.Raycast(target.transform.position, direction, distance);
            if (hit)
            {
                TileMonoBehaviour tile = hit.collider.GetComponent<TileMonoBehaviour>();
                if (tile && !tile.isOcupied)
                {
                    result[direction] = tile;
                }
            }
        }

        return result;
    }

    public static void SetFacingDirectionByAngle(float angle, out Vector2[] facingDirections)
    {
        facingDirections = new Vector2[2];

        foreach (var tuple in directionTuples)
        {
            if (angle >= tuple.Item1 && angle < tuple.Item2)
            {
                facingDirections[0] = tuple.Item3;
                facingDirections[1] = tuple.Item4;
                break;
            }
        }
    }

    public static void CalculateTileScore(GameObject calculateObject, int coverScore = StaticDataManager.SCORE_UNDER_COVER, int approachScore = 0)
    {
        Debug.Log("Target :: 准备计算目标Tile");


        // 1 寻找玩家的掩体位置
        Dictionary<Vector2, TileMonoBehaviour> NearbyTiles = GetNearbyTile(calculateObject, StaticDataManager.OBJECT_NEARBY_CHECK_DISTANCE);
        Debug.Log(NearbyTiles.Count);
        List<float> angles = new List<float>();

        Vector2[] checkDirections = new Vector2[] { Vector2.up, Vector2.right, Vector2.down, Vector2.left, Vector2.up };

        List<AnglePair> anglePairs = new List<AnglePair>();

        float minAngle = float.MaxValue;
        float maxAngle = float.MinValue;

        // 2 遍历玩家周围的格子，寻找没有掩体的格子的方向
        for (int i = 0; i < checkDirections.Length; i++)
        {
            if (!NearbyTiles.ContainsKey(checkDirections[i]))
            {
                // 检测到不存在的方向，需要处理了
                // 生成角度对
                if (minAngle != float.MinValue && maxAngle != float.MaxValue)
                {
                    anglePairs.Add(new AnglePair { min = Mathf.Ceil(minAngle), max = Mathf.Ceil(maxAngle) });

                    minAngle = float.MinValue;
                    maxAngle = float.MaxValue;
                }
            }

            float angle;

            // 最后一个 UP 应必须设置成360
            if (i == checkDirections.Length - 1)
            {
                angle = 360;
            }
            else
            {
                angle = Vector2.SignedAngle(checkDirections[i], Vector2.up);
            }

            if (angle < minAngle)   minAngle = angle;

            if (angle > maxAngle)   maxAngle = angle;
        }

        if (minAngle != float.MinValue && maxAngle != float.MaxValue)
        {
            anglePairs.Add(new AnglePair { min = minAngle, max = maxAngle });
        }

        //AnglePair anglePair = new AnglePair();
        //anglePair.min = angles[0];
        //anglePair.max = angles[angles.Count - 1];

        //[] anglePairs = new AnglePair[] { anglePair };

        foreach (var pair in anglePairs)
        {
            Debug.Log($"当前的角度对是 {pair.min}, {pair.max}");
        }


        // 2.1 然后找出最大的vectors和最小的vectors

        // 3 然后创建一个用于判断向量是否在向量范围内的函数 
        // 4 在遍历障碍物的时候，对障碍物进行这个函数的判断

        // 5 然后遍历所有障碍物
        // 6 计算玩家跟障碍物的距离向量
        // 7 根据向量的正负来判断哪个方向上面的为增减
        foreach (GameObject target in GameObject.FindGameObjectsWithTag(StaticDataManager.BLOCK_TAG))
        {
            Dictionary<Vector2, TileMonoBehaviour> targetNearbyTileDict = TileHelper.GetNearbyTile(target, StaticDataManager.OBJECT_NEARBY_CHECK_DISTANCE);

            Vector2 vector = calculateObject.transform.position - target.transform.position;
            vector.Normalize();

            foreach (Vector2 direction in new Vector2[] { Vector2.left, Vector2.up, Vector2.down, Vector2.right })
            {
                if (!targetNearbyTileDict.ContainsKey(direction)) { continue; }

                bool isInCover = false;

                // 获取direction跟player的位置的角度..?
                float angle = Mathf.Ceil(Vector2.SignedAngle((calculateObject.transform.position - targetNearbyTileDict[direction].transform.position).normalized, Vector2.up));
                if (angle < 0)
                {
                    angle += 360;
                }

                Vector2[] tileFacingDirections = new Vector2[2];
                TileHelper.SetFacingDirectionByAngle(angle, out tileFacingDirections);

                foreach (Vector2 coverDirection in tileFacingDirections)
                {
                    if (coverDirection == Vector2.zero)
                    {
                        continue;
                    }

                    RaycastHit2D hit = Physics2D.Raycast(targetNearbyTileDict[direction].gameObject.transform.position, coverDirection, StaticDataManager.OBJECT_NEARBY_CHECK_DISTANCE, LayerMask.GetMask(StaticDataManager.BLOCK_LAYER));

                    if (hit.collider)
                    {
                        isInCover = true;
                        break;
                    }
                }

                int score = isInCover ? coverScore : StaticDataManager.SCORE_WITHOUT_COVER;
                
                if (isTileCanCriticalHit(calculateObject.transform.position, targetNearbyTileDict[direction].transform.position, anglePairs))
                {
                    score += StaticDataManager.SCORE_CAN_CRITICAL_HIT;
                }

                TileHelper.AddScoreToTargetTile(target, direction, StaticDataManager.OBJECT_NEARBY_CHECK_DISTANCE, score);
            }
        }

        if (approachScore != 0)
        {
            float score;
            // TODO 如果这个选项为真
            // TODO 那么以玩家旁边的点数就会增加
            // TODO 遍历全场的Tiles
            foreach (GameObject tile in GameObject.FindGameObjectsWithTag(StaticDataManager.TILE_TAG))
            {
                score = Vector2.Distance(calculateObject.transform.position, tile.transform.position) * -approachScore;
                tile.GetComponent<TileMonoBehaviour>().AddScore((int)score);
            }
        }
    }

    public static bool isTileCanCriticalHit(Vector2 originVector, Vector2 targetVector, List<AnglePair> anglePairs)
    {
        Vector2 vector = targetVector - originVector;
        vector.Normalize();

        float angle = Mathf.Ceil(Vector2.SignedAngle(vector, Vector2.up));

        if (angle < 0)
        {
            angle += 360;
        }

        foreach (AnglePair anglePair in anglePairs)
        {
            if (angle >= anglePair.min && angle <= anglePair.max)
            {
                return true;
            }
        }

        return false;
    }
}
